// This file is part of OpenMeca, an easy software to do mechanical simulation.
//
// Author(s)    :  - Damien ANDRE  <openmeca@gmail.com>
//
// Copyright (C) 2012 Damien ANDRE
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.


#ifndef OpenMeca_Core_SmartSetOf_hpp
#define OpenMeca_Core_SmartSetOf_hpp

#include "OpenMeca/Core/SetOf.hpp"


namespace OpenMeca
{
  namespace Core
  {

    template<typename type>
    class SmartSetOf : public SetOf<type> 
    {
    public:

      SmartSetOf();
      SmartSetOf(const SmartSetOf &);
      SmartSetOf& operator=(const SmartSetOf &);
      type& operator()(unsigned int);

      ~SmartSetOf();

      it Begin();
      it End();

      const_it Begin() const;
      const_it End() const;

      unsigned int GetTotItemNumber() const;

      void AddItem(type&);
      
      void RemoveItem(type&);
      void Clear();
      void ClearAndDelete();
      void DeleteAllItem();
      //unsigned int GetRank (type *) const;
      unsigned int GetRank (const type *) const;
      void EraseDoublet();
      bool Contain(type *) const;
      bool Contain(type &) const;
      
      void CheckForDoublet();
      void UnCheckForDoublet();

      bool IsGlobalSet();

    private:
      friend class boost::serialization::access;
      template<class Archive> void save(Archive & ar, const unsigned int) const;
      template<class Archive> void load(Archive & ar, const unsigned int);
      BOOST_SERIALIZATION_SPLIT_MEMBER() 
      
    private:
      std::list<type *>  set_;
      bool checkForDoublet_;

    };

    template<typename type> 
    template<class Archive>
    inline void
    SmartSetOf<type>::save(Archive & ar, const unsigned int) const
    {
      std::cout << "SmartSetOf<type>::save" << std::endl;
      unsigned int totItemNumber = set_.size();
      ar << BOOST_SERIALIZATION_NVP(totItemNumber);
      typename std::list<type *>::const_iterator it;
      for (it=set_.begin() ; it != set_.end(); it++ )
	{
	  ar << BOOST_SERIALIZATION_NVP(*it);
	} 
    }

    template<typename type> 
    template<class Archive>
    inline void
    SmartSetOf<type>::load(Archive & ar, const unsigned int)
     {
       bool isGlobal = IsGlobalSet();
       unsigned int totItemNumber = 0;
       ar >> BOOST_SERIALIZATION_NVP(totItemNumber);
       for (unsigned int i=0;  i< totItemNumber; ++i)
	 {
	   type* ptr=0;
	   ar >> BOOST_SERIALIZATION_NVP(ptr);
	   assert(ptr != 0);

	   if (!isGlobal)
	     AddItem(ptr);
	 }
     }
      
    template<typename type> 
    inline
    SmartSetOf<type>::~SmartSetOf()
    {
      typename std::list<type *>::iterator it;
      for ( it=set_.begin() ; it != set_.end(); it++ )
	{
	  static_cast<AutoRegister<T>& >(item).RemoveSet(*this);
	}
    }

    
    template<typename type> 
    inline
    SmartSetOf<type>::SmartSetOf(const SmartSetOf &set)
      : set_(set.set_),
	checkForDoublet_(set.checkForDoublet_)
    {
    }
    
    template<typename type> 
    inline SmartSetOf<type>& 
    SmartSetOf<type>::operator=(const SmartSetOf &set)
    {
      set_ = set.set_;
      checkForDoublet_ = set_.checkForDoublet_;
      return *this;
    }


    template<typename type> 
    inline type& 
    SmartSetOf<type>::operator()(unsigned int i)
    {
      unsigned int index = 0;
      assert(i < GetTotItemNumber());
      typename std::list<type *>::iterator it;
      for (it=set_.begin() ; it != set_.end(); it++ )
	{
	  if (index == i)
	    return **it;
	  index++;
	} 
      assert(0);      
      return (**(Begin()));
    }


    template<typename type> 
    inline 
    SmartSetOf<type>::SmartSetOf()
      :set_(),
       checkForDoublet_(true)
					  
    {
    }
    
    template<typename type> 
    inline typename SmartSetOf<type>::it 
    SmartSetOf<type>::Begin()
    {
      return set_.begin();
    }

    template<typename type> 
    inline typename SmartSetOf<type>::it 
    SmartSetOf<type>::End()
    {
      return set_.end();
    }

    template<typename type> 
    inline typename SmartSetOf<type>::const_it 
    SmartSetOf<type>::Begin() const
    {
      return set_.begin();
    }

    template<typename type> 
    inline typename SmartSetOf<type>::const_it 
    SmartSetOf<type>::End() const
    {
      return set_.end();
    }

    
    template<typename type> 
    inline unsigned int 
    SmartSetOf<type>::GetTotItemNumber() const
    {
      return set_.size();
    }
        
    template<typename type>
    inline void
    SmartSetOf<type>::AddItem(type& item)
    {
      typename std::list<type *>::iterator it;
      for (it=set_.begin() ; it != set_.end(); it++ )
	{
	  if (*it==&item && checkForDoublet_==true)
	    {
	      assert(0);
	    }
	}
     set_.push_back(&item);
     static_cast<AutoRegister<T>& >(item).AddSet(*this);
    }
    
    template<typename type>
    inline void
    SmartSetOf<type>::EraseDoublet()
    {
      for (unsigned int i=0; i<GetTotItemNumber(); ++i)
	{
	  for (unsigned int j=i+1; j<GetTotItemNumber(); ++j)
	    {
	      if (set_[i]==set_[j])
		set_.erase(set_.begin()+j);
	    }
	}
    }

    template<typename type>
    inline void
    SmartSetOf<type>::RemoveItem(type& item)
    {
      typename std::list<type *>::iterator it;
      for ( it=set_.begin() ; it != set_.end(); it++ )
	{
	  if (*it==&item)
	    {
	      set_.erase(it);
	      static_cast<AutoRegister<T>& >(item).RemoveSet(*this);
	      return;
	    }
	}
      assert(false);
    }

   
    template<typename type>
    inline void
    SmartSetOf<type>::Clear()
    {
      set_.clear();
    }

    template<typename type>
    inline void
    SmartSetOf<type>::ClearAndDelete()
    {
      DeleteAllItem();
      set_.clear();
    }

     template<typename type>
    inline void
     SmartSetOf<type>::DeleteAllItem()
    {
      typename std::list<type *>::iterator it;
      while(GetTotItemNumber() > 0)
       {
	 const unsigned int size = GetTotItemNumber();
	 it=set_.begin();
	 delete (*it);
	 assert(GetTotItemNumber() < size);
       }
    }


    template<typename type>
    inline unsigned int
    SmartSetOf<type>::GetRank(const type * item) const
    {
      for (unsigned int i=0; i<GetTotItemNumber(); ++i)
	{
	  if (set_[i]==item)
	    {
	      return i;
	    }
	}
      assert(false); 
      return  0;
    }

    template<typename type>
    inline bool 
    SmartSetOf<type>::Contain(type* item) const
    {
      typename std::list<type *>::const_iterator it;
      for ( it=set_.begin() ; it != set_.end(); it++ )
	{
	  if ((*it) == item)
	    return true;
	}
      return  false;
    }

     template<typename type>
    inline bool 
    SmartSetOf<type>::Contain(type& item) const
    {
      return Contain(&item);
    }


    template<typename type>
    inline void 
    SmartSetOf<type>::CheckForDoublet()
    {
      checkForDoublet_ = true;
    }

    template<typename type>
    inline void 
    SmartSetOf<type>::UnCheckForDoublet()
    {
      checkForDoublet_ = false;
    }


    template<typename type>
    inline  
    SmartSetOf<type>::IsGlobalSet()
    {
      return (this == &AutoRegister<T>::GetAllItem());
    }

  }
}

#endif
